// the caching lock server implementation

#include "lock_server_cache.h"
#include <sstream>
#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>

static void *
revokethread(void *x)
{
  lock_server_cache *sc = (lock_server_cache *) x;
  sc->revoker();
  return 0;
}

static void *
retrythread(void *x)
{
  lock_server_cache *sc = (lock_server_cache *) x;
  sc->retryer();
  return 0;
}

lock_server_cache::lock_server_cache()
{
  pthread_mutex_init(&lock_map_mutex, NULL);
  pthread_cond_init(&lock_map_cond_mutex, NULL);

  pthread_t th;
  int r = pthread_create(&th, NULL, &revokethread, (void *) this);
  assert (r == 0);
  r = pthread_create(&th, NULL, &retrythread, (void *) this);
  assert (r == 0);
  
}

//print lock_info struct and waiting requests for id
void lock_server_cache::print_lock_info(lock_info l, lock_protocol::lockid_t id){
  if (l.status == LOCKED){
    printf("\tstatus: LOCKED\n");
  }
  if (l.status == FREE) {
    printf("\tstatus: FREE\n");
  }
  printf("\tcurrent: %s\n", (l.curr).c_str());
  printf("\twanting requestors: \n");
									
  //print waiting requests
  std::map<lock_protocol::lockid_t, std::list<std::string> >::iterator map_it;
  map_it = lock_wanters.find(id); 
  if (map_it != lock_wanters.end()){
    std::list<std::string> wanters = map_it->second; 
    std::list<std::string>::iterator it;
    for (it = (wanters).begin(); it!= (wanters).end(); it++){
      printf("\t\t %s\n", (*it).c_str());
    }
  }else {
    printf("\t\tnobody else wants the lock\n");
  }
}

//make new lock struct
lock_info lock_server_cache::make_new_lock_info(){
  lock_info l;
  l.status = FREE;
  l.curr = "";
  return l;
}

//make new lock struct with starting owner
lock_info lock_server_cache::make_new_lock_info(std::string id){
  lock_info l;
  l.status = LOCKED;
  l.curr = id;
  return l;
}

void
lock_server_cache::revoker()
{

  // This method should be a continuous loop, that sends revoke
  // messages to lock holders whenever another client wants the
  // same lock

    while (1){
      pthread_mutex_lock(&lock_map_mutex);
      std::map<lock_protocol::lockid_t, lock_info>::iterator it_lm;

      //traverse entire file
      for (it_lm = lock_map.begin();
	 it_lm != lock_map.end();
	   it_lm++){
      
	//if its locked and waiting requests list is not empty,
	//send revoke
	if ((it_lm->second).status == LOCKED){
	  
	  //find owner
	  std::map<lock_protocol::lockid_t, 
	    std::list<std::string> >::iterator it_lw;
	  
	  it_lw = lock_wanters.find(it_lm->first);
	  
	  //check if waiting requestors list exists
	  if (it_lw != lock_wanters.end()){
	    std::list<std::string>::iterator runner;
	    
	    //if its not empty, send revoke
	    if(!((it_lw->second).empty())){
	      std::string buf = (it_lm->second).curr;
	      rpcc* toRevoke= find_or_make_rpcc(buf);
	      int k;
	      toRevoke->call(rlock_protocol::revoke, it_lm->first, 0, k);
	    }
	  }
	}
      }
      pthread_mutex_unlock(&lock_map_mutex);
    }  
    
}

rpcc* lock_server_cache::find_or_make_rpcc(std::string buf){

  //find rpc for given string
  std::map<std::string, rpcc*>::iterator it;
  it = addr_book.find(buf);
  
  //if its not in the address book, make new one and store
  if (it == addr_book.end()){
    sockaddr_in dstsock;
    make_sockaddr(buf.c_str(), &dstsock);
    rpcc* ct = new rpcc(dstsock);
    ct->bind();
    addr_book[buf] = ct;
    return ct;
    

    //if it is in the map, return it
  } else {
    return it->second;
  }
  
}

void
lock_server_cache::retryer()
{
  // This method should be a continuous loop, waiting for locks
  // to be released and then sending retry messages to those who
  // are waiting for it.

  while (1){
    pthread_mutex_lock(&lock_map_mutex);
    
    std::map<lock_protocol::lockid_t, lock_info>::iterator it_lm;
    
    //walk through entire map
    for (it_lm = lock_map.begin();
	 it_lm != lock_map.end();
	 it_lm++){
      
      //if it is free and has waiting requests...send retry
      if ((it_lm->second).status == FREE){
	
	std::string owner = (it_lm->second).curr;
	std::map<lock_protocol::lockid_t, 
	  std::list<std::string> >::iterator it_lw;
	
	it_lw = lock_wanters.find(it_lm->first);
	
	//if waiting requesters list exists, check if its empty
	if (it_lw != lock_wanters.end()){
	  std::list<std::string>::iterator runner;
	  
	  //send retry to every requestor and delete from list
	  while (!((it_lw->second).empty())){
	    std::string buf = (it_lw->second).front();
	    rpcc* toRetry= find_or_make_rpcc(buf);
	    int k;
	    toRetry->call(rlock_protocol::retry, it_lm->first, 0, k);
	    (it_lw->second).pop_front();  
	  }
	}
      }
      
      
      
    }
    pthread_mutex_unlock(&lock_map_mutex);
  }  
  
}

lock_protocol::status 
lock_server_cache::release(lock_protocol::lockid_t lid, int &r){
  pthread_mutex_lock(&lock_map_mutex);
  std::map<lock_protocol::lockid_t, lock_info>::iterator it;
  it = lock_map.find(lid);

  //if its not in the map -- problem
  if (it == lock_map.end()){
    pthread_mutex_unlock(&lock_map_mutex);
    return lock_protocol::RPCERR;

    //else, set it to free 
  } else {
    (it->second).status = FREE;
    pthread_mutex_unlock(&lock_map_mutex);
    return lock_protocol::OK;
  }
}

lock_protocol::status
lock_server_cache::acquire(lock_protocol::lockid_t lid, std::string rpc_id, int &r){
  pthread_mutex_lock (&lock_map_mutex);
  map<lock_protocol::lockid_t, lock_info>::iterator it;
  it = lock_map.find(lid);
  
  //if its not the map, then make a new one and give it to requestor
  if (it == lock_map.end()){
    goto make_new_lock_client;
    
  } else { 
    // if lock is free, give it to requestor
    if ((it->second).status == FREE){
      lock_map.erase(lid);
      goto make_new_lock_client;
      
    }else {
      //someone has the lock, check if this client is already on
      //waiting requestors list
      int already_requested =0;
      std::map<lock_protocol::lockid_t, 
	std::list<std::string> >::iterator w_it;
      
      w_it = lock_wanters.find(lid);
      
      if (w_it != lock_wanters.end()){
	
	std::list<std::string>::iterator runner;
	
	for (runner = (w_it->second).begin();
	     runner != (w_it->second).end();
	     runner++){
	  
	  if (rpc_id.compare(*runner) == 0){
	    already_requested = 1;
	  }
	}
	
	//if not on list, add
	if (already_requested ==  0){
	  (w_it->second).push_back(rpc_id);
	}
	
	//create new list and add
      } else {
	std::list<std::string> new_list;
	new_list.push_back(rpc_id);
	lock_wanters[lid] = new_list;
      }
      
      pthread_mutex_unlock(&lock_map_mutex);
      return lock_protocol::RETRY;
    }
  }

  //make new lock, and add it;
 make_new_lock_client:
  lock_info l;
  l.status = LOCKED;
  l.curr = rpc_id;
  lock_map[lid] = l;
  
  map<lock_protocol::lockid_t, lock_info>::iterator check;
  check = lock_map.find(lid);
  
  pthread_mutex_unlock(&lock_map_mutex);
  return lock_protocol::OK;     
  
}
